package org.activequant.math.algorithms;

import org.apache.log4j.Logger;

/**
 * Kalman filter. Roughly equivalent to EMA.<br/>
 * Holds the following associated variables:
 * <ul>
 * <li>value(double)</li>
 * <li>p(double)</li>
 * <li>q(double)</li>
 * <li>numSamples(int)</li>
 * </ul>
 * <b>History:</b><br>
 *  - [20.02.2008] Created (Mike Kroutikov)<br>
 *
 *  @author Mike Kroutikov
 */
public class KalmanAccumulator {
	/**
	 * private static final double INFTY = 1.0e10;
	 */
	private static final double INFTY = 1.0e10;
	/**
	 * private double value = 0.0;
	 */
	private double value = 0.0; // immaterial
	/**
	 * private double p = INFTY;
	 */
	private double p = INFTY; // big initial measurement variance
	/**
	 * private double q;
	 */
	private double q;
	/**
	 * private int numSamples;
	 */
	private int numSamples;
	/**
	 * constructs a KalmanAccumulator using the given q(double) to set its associated q(double)
	 * @param q
	 */
	public KalmanAccumulator(double q) {
		this.q = q; // q = q;
	}
	
	private final Logger log = Logger.getLogger(getClass());
	
	/**
	 * Computed average. This value gets re-computed after every measurement.<br/>
	 * returns the associated value(double)
	 * @return computed value.
	 */
	public double getMeanValue() {
		return value; 
	}

	/**
	 * Total number of samples that this accumulator received.
	 * This value is recomputed after every measurement. One can check this and
	 * compare with the {@link #getPeriod() period} to get an idea if
	 * the accumulator has enough data to produce meaningful average.<br/>
	 * returns the associated numSamples(int)
	 * @return total length.
	 */
	public int getNumSamples() { return numSamples; }
	
	/**
	 * Call this to pass next measurement.
	 * After this function returns, following
	 * properties will be recomputed: {@link #getNumSamples() numSamples},
	 * {@link #getMeanValue() meanValue}.
	 * 
	 * @param input value of the signal.
	 */
	public void accumulate(double input) {
		p += q;
		double k = p / (p + 1.);
		value += k * (input - value);
		p = p / (p + 1.);
		
		numSamples ++;
		
		if(log.isDebugEnabled()) {
			log.debug("numSampels=" + numSamples + ", input=" + input + ", p=" + p + ", k=" + k + ", value=" + value);
		}
	}
	/**
	 * returns the square root of the associated p(double)
	 * @return
	 */
	public double getVariance() {
		return Math.sqrt(p);
	}
	/**
	 * returns a String in the form: "${value}(${numSamples} samples, variance=${p})"
	 */
	public String toString() {
		return "" + value + "(" + numSamples + " samples, variance=" + p + ")";
	}
}
